package ar.unc.famaf.pln.giveselector.classifier;

import give.formula.Atom;

import java.io.*;
import java.util.*;

import ar.unc.famaf.pln.giveselector.builders.ACLBuilder;
import ar.unc.famaf.pln.giveselector.context.Context;
import ar.unc.famaf.pln.giveselector.context.Utterance;
import ar.unc.famaf.pln.giveselector.loganalyser.LogAnalyser;
import ar.unc.famaf.pln.giveselector.selectors.ACLSelector;


/**
 * Classifies Utterance's Occurrences according to the following
 * criterion:
 * An Occurrence occ is classified as a SUCCESS (score = 1) if the first action
 * contained in the occ's executed actions list is equal to the
 * first element of the list of expected actions of the associated occ's Utterance (the 
 * occ's Utterance semantics). If it is not, then is classified
 * as a FAILURE (score = -1).
 * An Occurrence occ is classified as a BORROW_SUCCESS (score = 0.5) if occ is classified
 * as a SUCCESS and it is not the last occurrence instance of the occ's 
 * related occurrences list.
 * The BORROWED_FAILURE (score = -0.5) classification is defined similarly.
 * 
 * @author David Racca ( david.racca@gmail.com )
 *
 */
public class ACLClassifier implements IClassifier {


	/**
	 * Prints useful information about the classified utterances.
	 */
	public void printClassificationNumbers(Map<String, Set<Utterance>> utterances)
	{
		ACLSelector selector = new ACLSelector(utterances);
		Iterator<String> worldNames = utterances.keySet().iterator();

		System.out.println(" \n\n======================================================= " );
		System.out.println(" The next information is for determining the threshold " );
		System.out.println(" ======================================================= \n\n" );

		int allWorldMean = 0;
		int allWorldnUtt = 0;
		int allWorldnUttp = 0;
		int allWorldNullCalif = 0;
		
		Comparator<Utterance> comparator = Collections.reverseOrder(new UtteranceComparator(this));
		
		while (worldNames.hasNext())
		{
			String worldName = worldNames.next();
			System.out.println("\n==== WORLD: " + worldName + " ====\n");
			Set<Utterance> utts = utterances.get(worldName);
			allWorldnUtt += utts.size();
			int nUttp = 0;
			int nNullCalif = 0;
			int mean = 0;

			Iterator<Utterance> it = utts.iterator();

			while (it.hasNext())
			{
				Utterance utt = it.next();

				Context uttCtx = utt.getContext();
				Context context = new Context(uttCtx);
				context.setFutureActions(utt.getResponse().getDiscretizedActions());
				Set<Utterance> similars = selector.selectUtterances(worldName, context);
				List<Utterance> similarsList = new ArrayList<Utterance>();
				similarsList.addAll(similars);
				
				if (utt instanceof UtteranceP)
					++nUttp;
				
				System.out.println("\n\nUtterance: " + "[" + utt.getId() + "]: " + utt.getText());
				System.out.println("--------------------------------------------------------------------------------------------------");
				
				if (!similarsList.isEmpty())
				{
					Collections.sort(similarsList, comparator);
					mean += similarsList.size();
					System.out.println("Utts with similar reaction:");
					for (Utterance u : similarsList)
					{
						if (u instanceof UtteranceP)
						{
							float score = getScore(u);
							printUtteranceInfo((UtteranceP) u, score);
							if (u.getId() == utt.getId() && score == 0.f)
								++nNullCalif;
						}
						else
							System.out.println("n/i : " + u);
					}
				}
				else
					System.out.println("No similar Utts");
				
				System.out.println("--------------------------------------------------------------------------------------------------\n");
			}
			System.out.println("\n==== STATISTICS: " + worldName + "====\n");
			System.out.println("Average of the number of 'similars' per Utterance: " + ((float)mean)/((float)utts.size()));
			System.out.println("Percentage of Utterances with zero(=0) score: " 
					+ LogAnalyser.percentage(nNullCalif, nUttp) + "% (" + nNullCalif + "/" + nUttp + ")");
			System.out.println("\n=====================================");
			allWorldMean += mean;
			allWorldnUttp += nUttp; 
			allWorldNullCalif += nNullCalif;
		}

		System.out.println("\n\n==== STATISTICS FOR ALL THE WORLDS ====");
		System.out.println("\nNAverage of the number of 'similars' per Utterance for all the worlds: " + ((float)allWorldMean)/((float)allWorldnUtt));
		System.out.println("Percentage of Utterances with zero(=0) score: " 
				+ LogAnalyser.percentage(allWorldNullCalif, allWorldnUttp) + "% (" + allWorldNullCalif + "/" + allWorldnUttp +")");
		System.out.println("\n=====================================");
	}

	
	public void printUtteranceInfo(UtteranceP u, float score)
	{
		float nS = getNumberOf((UtteranceP) u, ClassifiedOccurrence.S);
		float nF = getNumberOf((UtteranceP) u, ClassifiedOccurrence.F);
		float nBS = getNumberOf((UtteranceP) u, ClassifiedOccurrence.BS);
		float nBF = getNumberOf((UtteranceP) u, ClassifiedOccurrence.BF);
		System.out.println(score + " : [" + u.getId() + "]: " 
			+ u.getText() + " [ S:" + nS + ", F:" + nF + ", BS:" + nBS + ", BF:" + nBF + " ]");
	}

	/**
	 * Prints useful information regarding to all the classified utterances
	 */
	public void printClassificationInfo(Map<String, Set<Utterance>> utterances)
	{
		int wTotalS = 0;
		int wTotalF = 0;
		int wTotalBS = 0;
		int wTotalBF = 0;;
		int wTotal = 0;;
		int nNoInfo = 0;;
		int nTotalUttp = 0;;
		int nTotalUtt = 0;;
		Iterator<String> worldNames = utterances.keySet().iterator();
		
		while (worldNames.hasNext())
		{
			String worldName = worldNames.next();
			System.out.println("\n\n=== World: " + worldName + " ===\n\n");
			Set<Utterance> utts = utterances.get(worldName);
		
			if (utts != null)
			{
				Iterator<Utterance> it = utts.iterator();
				int totalS = 0;
				int totalF = 0;
				int totalBS = 0;
				int totalBF = 0;
				int total = 0;
				int nUttp = 0;
				
				while (it.hasNext())
				{
					Utterance utt = it.next();
					if (utt instanceof UtteranceP)
					{
						System.out.println("Utterance: " + utt);
						List<Occurrence> occurrences = ((UtteranceP) utt).getOccurrences();
						String line = "";
						int nS = 0;
						int nF = 0;
						int nBS = 0;
						int nBF = 0;
						for (Occurrence o : occurrences)
						{
							if (o instanceof ClassifiedOccurrence)
							{
								float score = ((ClassifiedOccurrence) o).getScore();
								if (score == ClassifiedOccurrence.S)
								{
									line += "S  ";
									++nS;
								}
								else if (score == ClassifiedOccurrence.F)
								{
									line += "F  ";
									++nF;
								}
								else if (score == ClassifiedOccurrence.BS)
								{
									line += "BS  ";
									++nBS;
								}
								else
								{
									line += "BF  ";
									++nBF;
								}
							}
						}
						System.out.println("--- " + line + " ---");
						System.out.println("S: " + nS);
						System.out.println("F: " + nF);
						System.out.println("BS: " + nBS);
						System.out.println("BF: " + nBF);
						System.out.println("Total: " + occurrences.size());
						System.out.println("");
						totalS += nS;
						totalF += nF;
						totalBS += nBS;
						totalBF += nBF;
						total += occurrences.size();
						++nUttp;
					}
				}
				System.out.println("\n--- TOTALS " + worldName + "--- ");
				System.out.println("total S: " + totalS + " (" + LogAnalyser.percentage(totalS, total) + "%)");
				System.out.println("total F: " + totalF + " (" + LogAnalyser.percentage(totalF, total) + "%)");
				System.out.println("total BS: " + totalBS + " (" + LogAnalyser.percentage(totalBS, total) + "%)");
				System.out.println("total BF: " + totalBF + " (" + LogAnalyser.percentage(totalBF, total) + "%)");
				int noinfo = total-totalS-totalF-totalBS-totalBF;
				System.out.println("No info: " + noinfo + " (" + LogAnalyser.percentage(noinfo, total) + "%)");
				System.out.println("total Occurences: " + total + " (100%)\n");
				System.out.println("Utterances with info about performance: " + nUttp + " (" + LogAnalyser.percentage(nUttp, utts.size()) + "%)");
				System.out.println("Total Utterances: " + utts.size() + " (100%)");
				wTotalS += totalS;
				wTotalF += totalF;
				wTotalBS += totalBS;
				wTotalBF += totalBF;
				wTotal += total;
				nNoInfo += noinfo;
				nTotalUttp += nUttp;
				nTotalUtt += utts.size();
			}
		}
		System.out.println("\n\n===== All Worlds TOTALS ====\n");
		System.out.println("S: " + wTotalS + " (" + LogAnalyser.percentage(wTotalS, wTotal) + "%)");
		System.out.println("F: " + wTotalF + " (" + LogAnalyser.percentage(wTotalF, wTotal) + "%)");
		System.out.println("BS: " + wTotalBS + " (" + LogAnalyser.percentage(wTotalBS, wTotal) + "%)");
		System.out.println("BF: " + wTotalBF + " (" + LogAnalyser.percentage(wTotalBF, wTotal) + "%)");
		System.out.println("No info: " + nNoInfo + " (" + LogAnalyser.percentage(nNoInfo, wTotal) + "%)");
		System.out.println("total Occurrences: " + wTotal + " (100%)\n");
		System.out.println("Utterances with info about performance: " + nTotalUttp + " (" + LogAnalyser.percentage(nTotalUttp, nTotalUtt) + "%)");
		System.out.println("Total Utterances: " + nTotalUtt + " (100%)");
	}


	/**
	 * Runs the ACLclassifier over a file with annotated utterances
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException
	{
		String redirectOutput = null;
		String annotationFile = null;

		for (int i=0; i<args.length; i++)
		{
			String arg = args[i];
			if (arg.contentEquals("-r") || arg.contentEquals("--redirect"))
					redirectOutput = (i>=args.length-1) ? null : args[i+1];
			else if (arg.contentEquals("-a") || arg.contentEquals("--annotation"))
				annotationFile = (i>=args.length-1) ? null : args[i+1];
		}

		// Redirecting standard output to a file
		if (redirectOutput != null) 
		{
			System.setOut(
				new PrintStream(
					new BufferedOutputStream(
						new FileOutputStream(redirectOutput))));
		}

		if (annotationFile != null)
		{
			Map<String, Set<Utterance>> utterances = LogAnalyser.load(new File(annotationFile));
			ACLClassifier classifier = new ACLClassifier();
			Iterator<String> keys = utterances.keySet().iterator();
			while (keys.hasNext())
			{
				String worldName = keys.next();
				Set<Utterance> utts = classifier.classifyAll(utterances.get(worldName));
				utterances.put(worldName, utts);
			}
			classifier.printClassificationInfo(utterances);
			classifier.printClassificationNumbers(utterances);
			ACLBuilder.save(new File("datas/action-utterances-with-id-classified-performance.bin.gz"), utterances);
		}
		else
		{
			System.out.println("You must specify at least the annotation file location: ");
			System.out.println("-a <annotation_file_location>");
			System.out.println("-annotation <annotation_file_location>");
		}

	}


	/**
	 * Classifies all the Occurrences of all the given Utterances
	 * according to the following classification criteria:
	 * An occurrence occ will be classified as a SUCCESS if the first action
	 * of the list of executed actions associated with the occ
	 * is contained in the list of expected actions of occ's utterance 
	 * owner. Otherwise, it will be classified as a FAILURE.
	 * An occurrence occ is classified as a BORROW_SUCCESS if
	 * occ was classified as a SUCCESS and it is not the last
	 * occurrence of the occ's related occurrences list.
	 * 
	 * @return A new Set of Utterance objects all of which has
	 * been classified.
	 */
	@Override
	public Set<Utterance> classifyAll(Set<Utterance> utts) 
	{
		Set<Utterance> result = new HashSet<Utterance>();
		Iterator<Utterance> it = utts.iterator();
		while (it.hasNext())
		{
			Utterance utt = it.next();

			if (utt instanceof UtteranceP)
			{
				UtteranceP uttp = classify((UtteranceP) utt);
				result.add(uttp);
			}
			else
				result.add(utt);
		}
		return result;
	}


	/**
	 * Classifies all the occurrences from the given utterance using
	 * the following criteria:
	 * An occurrence occ will be classified as a SUCCESS if the first action
	 * of the list of executed actions associated with the occ
	 * is contained in the list of expected actions of occ's utterance 
	 * owner. Otherwise, it will be classified as a FAILURE.
	 * An occurrence occ is classified as a BORROW_SUCCESS if
	 * occ was classified as a SUCCESS and it is not the last
	 * occurrence of the occ's related occurrences list.
	 * 
	 * @return a copy of the given UtteranceP instance which
	 * contains ClassifiedOccurrences objects intead of 
	 * regular Occurrences.
	 */
	@Override
	public UtteranceP classify(UtteranceP utt) 
	{
		UtteranceP result = new UtteranceP(utt);

		for (Occurrence o : utt.getOccurrences())
		{
			ClassifiedOccurrence co = classify(o);
			if (co != null)
				result.addOccurrence(co);
			else
				result.addOccurrence(o);
		}
		return result;
	}


	/**
	 * Determines how the occ Occurrence instance is classified.
	 * An occurrence occ will be classified as a SUCCESS if the first action
	 * of the list of executed actions associated with the occ
	 * is contained in the list of expected actions of occ's utterance 
	 * owner. It will be classified as a FAILURE otherwise.
	 * An occurrence occ is classified as a BORROW_SUCCESS if
	 * occ was classified as a SUCCESS and it is not the last
	 * occurrence of the occ's related occurrences list.
	 *  
	 * @return null if cannot classify the Occurrence. 
	 * A new ClassifiedOccurrence instance is returned otherwise.
	 */
	@Override
	public ClassifiedOccurrence classify(Occurrence occ)
	{	
		ClassifiedOccurrence co = null;
		
		List<Atom> expectedReaction = occ.getOwner().getResponse().getDiscretizedActions();

		occ.sortRelatedOccurrences();
		List<Occurrence> related = occ.getRelatedOccurrences();
		Occurrence last = null;
		if (related.isEmpty()){
			System.out.println("Empty!!!" + occ);
			return null;
		}
		
		last = related.get(related.size()-1);
		List<Atom> executedActions = last.getResponse().getDiscretizedActions();
		
		if (executedActions != null && !executedActions.isEmpty())
		{
			Atom first = executedActions.get(0);
			float score = ClassifiedOccurrence.S;
			
			// An occ instance belongs of its own relatedOccurrences list
			if (occ.getTimestamp().equals(last.getTimestamp()))
				score = (expectedReaction.contains(first)) ? 
						ClassifiedOccurrence.S : ClassifiedOccurrence.F;
			// Checks for borrowed_success
			else
				score = (expectedReaction.contains(first)) ?
						ClassifiedOccurrence.BS : ClassifiedOccurrence.BF;

			co = new ClassifiedOccurrence(occ, score);
		}

		return co;
	}


	/**
	 * @return The grade for the given Utterance based on its list
	 * of ClassifiedOccurrences without considering the BORROWED kind of
	 * ClassifiedOccurrences.
	 */
	@Override
	public float getScore(Utterance utt)
	{
		float result = 0f;
		if (utt instanceof UtteranceP)
		{
			UtteranceP uttp = (UtteranceP) utt;
			float nS = getNumberOf(uttp, ClassifiedOccurrence.S);
			float nF = getNumberOf(uttp, ClassifiedOccurrence.F);
			result = nS - nF;
		}
		return result;
	}


	/**
	 * @return The number of occurrences which have been classified
	 * as c.
	 */
	public float getNumberOf(UtteranceP uttp, float score)
	{
		float result = 0.f;
		for (Occurrence o: uttp.getOccurrences())
		{
			if (o instanceof ClassifiedOccurrence)
			{
				float s = ((ClassifiedOccurrence) o).getScore();
				if (s == score)
					++result;
			}
		}
		return result;
	}

}
